
## 控制反转
控制反转（Inversion Of Controller）,缩写为 IOC。

```java
public class UserServiceTest {
  public static boolean doTest() {
    // ...
  }

  public static void main(String[] args) {//这部分逻辑可以放到框架中
    if (doTest()) {
      System.out.println("Test succeed.");
    } else {
      System.out.println("Test failed.");
    }
  }
}
```
在上面的代码中，所有的流程都由程序员来控制。

如果抽象出下面这样一个框架，来实现同样的功能，具体的代码实现如下所示：

```java
public abstract class TestCase {
  public void run() {
    if (doTest()) {
      System.out.println("Test succeed.");
    } else {
      System.out.println("Test failed.");
    }
  }

  public abstract boolean doTest();
}

public class JunitApplication {
  private static final List<TestCase> testCases = new ArrayList<>();

  public static void register(TestCase testCase) {
    testCases.add(testCase);
  }

  public static final void main(String[] args) {
    for (TestCase case: testCases) {
      case.run();
    }
  }
```

把这个简化版本的测试框架引入到工程中之后，只需要在框架预留的扩展点，也就是 TestCase 类中的 doTest() 抽象函数中，填充具体的测试代码就可以实现之前的功能了，完全不需要写负责执行流程的 main() 函数了。


```java
public class UserServiceTest extends TestCase {
  @Override
  public boolean doTest() {
    // ...
  }
}

// 注册操作还可以通过配置的方式来实现，不需要程序员显示调用register()
JunitApplication.register(new UserServiceTest();
```
上面这个例子就是典型的通过框架实现“控制反转”的例子。框架提供了一个可扩展的代码骨架，用来组装对象、管理整个执行流程。程序员利用框架进行开发的时候，只需要往预留的扩展点上，添加跟自己业务相关的代码，就可以利用框架来驱动整个程序流程的执行。


这里的控制指的是对程序执行流程的控制，而反转指的是在没有使用框架之前，程序员自己控制整个程序的执行。在使用框架之后，整个程序的执行流程可以通过框架来控制。流程的控制权从程序员反转到了框架。

实现控制反转的方法有很多，除了上面例子中的类似于模板设计模式的方法之外，还有依赖注入等方法。

所以，控制反转并不是一种具体的实现技巧，而是一个比较笼统的设计思想，一般用来知道框架层膜的设计。

## 依赖注入（DI）
依赖注入（Dependency Injection）跟控制反转恰恰相反，它是一种具体的编码技巧。用一句话来概况就是：<RedSpan>不通过 new() 的方式在类内部创建依赖类对象，而是将依赖的类对象在外部创建之后，通过构造函数、函数参数等方式传递（或注入）给类使用。</RedSpan>

在下面这个例子中，Notification 类负责消息推送，依赖 MessageSender 类实现商品推销、验证码等消息给用户。分别用依赖注入和非依赖注入两种方式来实现一下。具体的实现代码如下所示：

```java
// 非依赖注入实现方式
public class Notification {
  private MessageSender messageSender;

  public Notification() {
    this.messageSender = new MessageSender(); //此处有点像hardcode
  }

  public void sendMessage(String cellphone, String message) {
    //...省略校验逻辑等...
    this.messageSender.send(cellphone, message);
  }
}

public class MessageSender {
  public void send(String cellphone, String message) {
    //....
  }
}
// 使用Notification
Notification notification = new Notification();

// 依赖注入的实现方式
public class Notification {
  private MessageSender messageSender;

  // 通过构造函数将messageSender传递进来
  public Notification(MessageSender messageSender) {
    this.messageSender = messageSender;
  }

  public void sendMessage(String cellphone, String message) {
    //...省略校验逻辑等...
    this.messageSender.send(cellphone, message);
  }
}
//使用Notification
MessageSender messageSender = new MessageSender();
Notification notification = new Notification(messageSender);
```

通过依赖注入的方式将依赖的类对象传递进来，这样就提高了代码的扩展性，可以灵活地替换依赖的类。

上面的代码还有优化的空间，就是将 MessageSender 定义成接口，基于接口而非实现编程。

```java
public class Notification {
  private MessageSender messageSender;
  
  public Notification(MessageSender messageSender) {
    this.messageSender = messageSender;
  }
  
  public void sendMessage(String cellphone, String message) {
    this.messageSender.send(cellphone, message);
  }
}

public interface MessageSender {
  void send(String cellphone, String message);
}

// 短信发送类
public class SmsSender implements MessageSender {
  @Override
  public void send(String cellphone, String message) {
    //....
  }
}

// 站内信发送类
public class InboxSender implements MessageSender {
  @Override
  public void send(String cellphone, String message) {
    //....
  }
}

//使用Notification
MessageSender messageSender = new SmsSender();
Notification notification = new Notification(messageSender);
```

## 依赖控制框架(DI Framework)

通过依赖注入框架提供的扩展点，简单配置一下所有需要类及类之间的依赖关系，就可以实现由框架来自动创建对象、管理对象的生命周期、依赖注入等原本需要程序员来做的事情。
## 依赖反转原则
Dependency Inversion Principle，缩写为 DIP，具体描述为 <RedSpan>高层模块不要依赖低层模块。二者应该通过抽象来相互依赖。除此之外，抽象不要依赖具体实现细节，具体实现细节依赖抽象</RedSpan>

所谓高层模块和底层模块的划分，简单来说就是，在调用链上，调用者属于高层，被调用者属于底层。

在平时的业务开发中，高层模块依赖底层模块是没有任何问题的。

这条原则主要还是用来用来指导框架层面的设计。以 Tomcat 这个 Servlet 容器为例：

Tomcat 是运行 Java Web 应用程序的容器。Web 应用程序代码只需要部署在 Tomcat 容器下，便可以被 Tomcat 容器调用执行。Tomcat 是高层模块，Web 应用程序是底层模块。Tomcat 和应用程序之间没有直接的依赖关系，两者都依赖同一个抽象，也就是 Servlet 规范。

Servlet 规范不依赖具体的 Tomcat 容器和应用程序的实现细节，而 Tomcat 容器和应用程序依赖 Servlet 规范。
